// InstanceFactory 应用实例操作方法聚合，为上层domain提供基础调用
package aggregate

import (
	"errors"
	"fmt"
	"gluenet/internal/glue-srvp/domain/dependency"
	"gluenet/internal/glue-srvp/domain/entity"
	"gluenet/internal/glue-srvp/domain/po"
	"gluenet/internal/glue-srvp/domain/vo"
	"gluenet/internal/pkg/infra"
	"gluenet/internal/pkg/infra/prepare"
	"reflect"
	"time"
)

func init() {
	//fmt.Printf("init config factory\n")
	prepare.RegisterFactory(func() {
		intanceFactory := NewInstaceFactory()
		prepare.InjectDependency(func() *InstanceFactory {
			return intanceFactory
		})
	})
}

type InstanceFactory struct {
	ConfigMetaRepo   dependency.ConfigMetaRepo
	InstanceMetaRepo dependency.InstanceMetaRepo
	GuidCLI          dependency.GuidCLI
	InstanceCLI      dependency.InstanceCLI
	MetricCLI        dependency.MetricCLI
}

func (r *InstanceFactory) AddInstance(auth infra.AuthInfo, req vo.CreateInstanceReq) (infra.Entity, error) {
	var (
		err  error
		cfgs []entity.Config
		agt  entity.Agent
	)

	if err = r.ConfigMetaRepo.FindConfig(&cfgs, auth, map[string]interface{}{
		"srvp_config.id": req.ConfigID,
	}); err != nil {
		return nil, err
	}
	if len(cfgs) == 0 {
		return nil, infra.DataNotFind
	}
	if len(cfgs) > 1 {
		return nil, fmt.Errorf("much config use same id")
	}

	if err = r.ConfigMetaRepo.FindOne(&agt, map[string]interface{}{
		"guid": req.Cluster,
	}); err != nil {
		return nil, err
	}

	var cfg = cfgs[0]
	var obj = entity.Instance{}

	obj.GUID = ""
	obj.ConfigID = cfg.Id
	obj.Name = req.Name
	obj.Describe = cfg.Describe
	obj.Version = 0
	obj.Kind = cfg.Kind
	obj.Property = cfg.Property
	obj.Platform = req.Platform
	obj.Origin = infra.FromPlatform
	obj.DetailRepoValue = cfg.DetailRepoValue
	obj.RunTimeRepoValue = po.InstanceRuntime{
		ClusterAgentGuid: req.Cluster,
		ClusterName:      agt.DetailRepoValue.ClusterName,
	}
	obj.Config = &cfg

	// register guid
	rOrder := &entity.RegisterOrder{}
	rOrder.RegisterOrder.ObjectKind = obj.Kind
	rOrder.RegisterOrder.ObjectPlatform = obj.Platform
	rOrder.RegisterOrder.ObjectDetail = obj
	obj.GUID, err = r.GuidCLI.Register(rOrder)
	if err != nil {
		return nil, err
	}

	if err = r.InstanceCLI.CreateObject(&obj); err != nil {
		return nil, err
	}

	if err = r.InstanceMetaRepo.AddInstance(&obj, auth); err != nil {
		r.InstanceCLI.RemoveObject(&obj)
		return nil, err
	}

	return obj, nil
}

func (r *InstanceFactory) DelImportInstance(auth infra.AuthInfo, guid string) (infra.Entity, error) {
	var obj entity.Instance
	if err := r.InstanceMetaRepo.FindOne(&obj, "guid = ? and platform = ?", guid, infra.FromExternal); err != nil {
		return nil, err
	}
	if err := r.InstanceMetaRepo.DelInstance(&obj, auth); err != nil {
		return nil, err
	}

	return obj, nil
}

// 需要修复
func (r *InstanceFactory) ImportInstance(auth infra.AuthInfo, req *vo.ImportInstanceReq) (infra.Entity, error) {
	var obj = &entity.Instance{}

	obj.GUID = req.Guid
	obj.Platform = req.Platform
	obj.Name = req.Name
	obj.Describe = req.Describe
	obj.Version = 0
	obj.Kind = infra.ConfigKindKubeYaml
	obj.Property = infra.PropertyApplication
	obj.Origin = infra.FromExternal
	obj.DetailRepoValue = po.ConfigDetail{}
	obj.RunTimeRepoValue = po.InstanceRuntime{
		ClusterAgentGuid: req.AgentGuid,
	}

	if err := r.InstanceMetaRepo.AddInstance(obj, auth); err != nil {
		return nil, err
	}

	return obj, nil
}

func (r *InstanceFactory) Findby(auth infra.AuthInfo, query map[string]interface{}) ([]entity.Instance, error) {
	var objs []entity.Instance

	query2 := make(map[string]interface{}, len(query))
	for k, v := range query {
		query2["srvp_instance."+k] = v
	}
	err := r.InstanceMetaRepo.FindInstance(&objs, auth, query2)
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, err
	}

	return objs, nil
}

func (r *InstanceFactory) UptInstance(auth infra.AuthInfo, req vo.UpdateInstanceReq) (*entity.Instance, error) {
	var obj entity.Instance

	if err := r.InstanceMetaRepo.FindOne(&obj, map[string]interface{}{
		"id": req.Id,
	}); err != nil {
		return nil, err
	}
	obj.Name = req.Name
	obj.Describe = req.Describe

	err := r.InstanceMetaRepo.UptInstance(&obj, auth)
	if err != nil {
		return nil, err
	}

	return &obj, nil
}

func (r *InstanceFactory) DelInstance(auth infra.AuthInfo, id int64) (*entity.Instance, error) {
	var obj entity.Instance

	err := r.InstanceMetaRepo.FindOne(&obj, map[string]interface{}{
		"id": id,
	})
	if err != nil {
		return nil, err
	}

	if obj.Origin != infra.FromPlatform {
		return nil, fmt.Errorf("app %v is form other platform, guid %v", obj.Name, obj.GUID)
	}

	if err = r.InstanceCLI.RemoveObject(&obj); err != nil {
		return nil, err
	}

	if err = r.InstanceMetaRepo.DelInstance(&obj, auth); err != nil {
		return nil, err
	}

	return &obj, nil
}

func (r *InstanceFactory) FindInstanceLinks(auth infra.AuthInfo, guid string) (interface{}, error) {
	var links []entity.Link
	err := r.InstanceMetaRepo.FindBy(&links, map[string]interface{}{
		"stg_guid": guid,
	})
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, err
	}

	var apps []entity.Instance
	err = r.InstanceMetaRepo.FindInstance(&apps, auth, map[string]interface{}{
		"property": infra.PropertyApplication,
	})
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, err
	}

	if len(apps) < len(links) {
		return nil, fmt.Errorf("apps count < links")
	}

	linked, i := make([]string, len(links)), 0
	unlinked, j := make([]string, len(apps)-len(links)), 0

	if len(links) == 0 {
		for k, app := range apps {
			unlinked[k] = app.GUID
		}
	} else {
		for _, app := range apps {
			mark := true
			for _, link := range links {
				if app.GUID == link.AppGUID {
					mark = false
				}
			}
			if mark {
				unlinked[j] = app.GUID
				j++
			} else {
				linked[i] = app.GUID
				i++
			}
		}
	}

	return map[string]interface{}{
		"linked":   linked,
		"unlinked": unlinked,
	}, nil
}

func (r *InstanceFactory) LinkInstance(auth infra.AuthInfo, cid, oid string) (*entity.Link, error) {
	var (
		err        error
		stgs, apps []entity.Instance
	)

	if err = r.InstanceMetaRepo.FindInstance(&stgs, auth, map[string]interface{}{
		"guid":     cid,
		"property": infra.PropertyStrategy,
	}); err != nil {
		return nil, err
	}
	if len(stgs) == 0 {
		return nil, errors.New("controll strategy doesn't exsit")
	}
	if err = r.InstanceMetaRepo.FindInstance(&apps, auth, map[string]interface{}{
		"guid":     oid,
		"property": infra.PropertyApplication,
	}); err != nil {
		return nil, err
	}
	if len(apps) == 0 {
		return nil, errors.New("applicaton doesn't exsit")
	}

	var lk = entity.Link{}
	lk.StyGUID = cid
	lk.AppGUID = oid

	_, err = r.InstanceMetaRepo.Add(&lk)
	if err != nil {
		return nil, err
	}
	return &lk, nil
}

func (r *InstanceFactory) DelAllLink(auth infra.AuthInfo, cid string) error {
	var obj []entity.Instance
	if err := r.InstanceMetaRepo.FindInstance(&obj, auth, map[string]interface{}{
		"guid": cid,
	}); err != nil {
		return err
	}
	if len(obj) == 0 {
		return infra.AuthFailed
	}

	var link = entity.Link{}
	link.StyGUID = cid

	_, err := r.InstanceMetaRepo.Del(&link)
	if err != nil {
		return err
	}
	return nil
}

func (r *InstanceFactory) UpdateInstanceContainers(auth infra.AuthInfo) error {
	var (
		err  error
		objs []entity.Instance
	)
	if err = r.InstanceMetaRepo.FindInstance(&objs, auth, map[string]interface{}{
		"origin": infra.FromPlatform,
	}); err != nil {
		return err
	}

	for _, obj := range objs {
		r.MetricCLI.UpdateInstanceContainerMetric(&obj)
		if err = r.InstanceMetaRepo.UptInstance(&obj, auth); err != nil {
			return err
		}
	}
	return nil
}

func (r *InstanceFactory) GetInstanceContainerAgent(guid, container string) (interface{}, error) {
	return nil, nil
}

func (r *InstanceFactory) InfoStrategy(sGuid string, aGuids []string, userID string) (interface{}, error) {
	return nil, nil
}

func (r *InstanceFactory) Runtime(auth infra.AuthInfo, query map[string]interface{}) (interface{}, error) {
	var objs []entity.Instance

	query2 := make(map[string]interface{}, len(query))
	for k, v := range query {
		query2["srvp_instance."+k] = v
	}
	err := r.InstanceMetaRepo.FindInstance(&objs, auth, query2)
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, err
	}

	var res = make([]vo.InstanceRuntimeRes, len(objs))
	for k, v := range objs {
		ars := vo.InstanceRuntimeRes{
			GUID:           v.GUID,
			Name:           v.Name,
			Platform:       v.Platform,
			Version:        v.Version,
			Describe:       v.Describe,
			UsedCores:      10,
			AvgMemUsed:     10,
			ErrorLogsCount: 10,
			RunningTime:    time.Now().Sub(v.Created).Seconds(),
		}
		//var podInfo = make(map[string]map[string]interface{}, len(v.RunTimeRepoValue.PodsOnNodes))
		//for k, v := range v.RunTimeRepoValue.PodsOnNodes {
		//	podInfo
		//}

		res[k] = ars
	}

	var insRes = make([]interface{}, len(res)+1)
	var tags []interface{}
	for k, obj := range res {
		if k == 0 {
			e := reflect.TypeOf(obj)
			num := e.NumField()
			tags = make([]interface{}, num)
			for i := 0; i < num; i++ {
				tags[i] = e.Field(i).Tag.Get(`json`)
			}
			insRes[0] = tags
		}

		var vals = make([]interface{}, len(tags))
		e := reflect.ValueOf(obj)
		for fid := range tags {
			val := e.Field(fid).Interface()
			vals[fid] = val
		}
		insRes[k+1] = vals
	}
	return insRes, nil

	//return insRes, nil
}

func (r *InstanceFactory) RuntimeChildInfo(auth infra.AuthInfo, query map[string]interface{}) (interface{}, error) {
	var objs []entity.Instance

	query2 := make(map[string]interface{}, len(query))
	for k, v := range query {
		query2["srvp_instance."+k] = v
	}
	err := r.InstanceMetaRepo.FindInstance(&objs, auth, query2)
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, err
	}

	var res = make(map[string]*vo.InstanceRuntimeChild, len(objs))
	for _, v := range objs {
		app := vo.InstanceRuntimeChild{}
		app.Guid = v.GUID
		app.AgentGuid = v.RunTimeRepoValue.ClusterAgentGuid
		app.AppName = v.Name
		app.Property = v.Property
		app.Pods = make(map[string]*vo.InstancePodInfo)
		for podName, containName := range v.RunTimeRepoValue.ContainerInPods {
			if _, ok := app.Pods[podName]; !ok {
				podN := vo.InstancePodInfo{}
				podN.PodName = podName
				podN.PodContainers = make(map[string]*vo.InstanceContainerInfo)
				podN.PodService = v.RunTimeRepoValue.PodsService[podName]
				app.Pods[podName] = &podN
			}
			var container = vo.InstanceContainerInfo{}
			container.ContainerName = containName
			container.ContainerFlame = v.DetailRepoValue.OptionFlame

			app.Pods[podName].PodContainers[containName] = &container
		}
		res[v.GUID] = &app
	}

	return res, nil
}

func NewInstaceFactory() *InstanceFactory {
	factory := &InstanceFactory{}
	prepare.FetchDependency(&factory.ConfigMetaRepo)
	prepare.FetchDependency(&factory.InstanceMetaRepo)
	prepare.FetchDependency(&factory.InstanceCLI)
	prepare.FetchDependency(&factory.GuidCLI)
	prepare.FetchDependency(&factory.MetricCLI)
	return factory
}
